//  Neighborhood.java
//
//  Author:
//       Antonio J. Nebro <antonio@lcc.uma.es>
//       Juan J. Durillo <durillo@lcc.uma.es>
//
//  Copyright (c) 2011 Antonio J. Nebro, Juan J. Durillo
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

package etmo.util;

import etmo.core.SolutionSet;

/**
 * Class representing neighborhoods for a <code>Solution</code> into a
 * <code>SolutionSet</code>.
 */
public class Neighborhood {

	/**
	 * Maximum rate considered
	 */
	private static int MAXRADIO = 2;

	/**
	 * Stores the neighborhood. structure_ [i] represents a neighborhood for a
	 * solution. structure_ [i][j] represents a neighborhood with a ratio.
	 * structure_ [i][j][k] represents a neighbor of a solution.
	 */
	private int[][][] structure_;

	/**
	 * Stores the size of the solutionSet.
	 */
	private int solutionSetSize_;

	/**
	 * Stores the size for each row
	 */
	private int rowSize_;

	/**
	 * Enum type for defining the North, South, East, West, North-West,
	 * South-West, North-East, South-East neighbor.
	 */
	enum Row {
		N, S, E, W, NW, SW, NE, SE
	}

	/**
	 * Constructor. Defines a neighborhood of a given size.
	 * 
	 * @param solutionSetSize
	 *            The size.
	 */
	public Neighborhood(int solutionSetSize) {
		solutionSetSize_ = solutionSetSize;
		// Create the structure_ for store the neighborhood
		structure_ = new int[solutionSetSize_][MAXRADIO][];

		// For each individual, and different rates the individual has a
		// different
		// number of neighborhoods
		for (int ind = 0; ind < solutionSetSize_; ind++) {
			for (int radio = 0; radio < MAXRADIO; radio++) {
				if (radio == 0) {// neighboors whit rate 1
					structure_[ind][radio] = new int[8];
				} else if (radio == 1) { // neighboors whit rate 2
					structure_[ind][radio] = new int[24];
				} // if
			} // for
		} // for

		// Calculate the size of a row
		rowSize_ = (int) Math.sqrt((double) solutionSetSize_);

		// Calculates the neighbors of a individual
		for (int ind = 0; ind < solutionSetSize_; ind++) {
			// rate 1
			// North neighbors
			if (ind > rowSize_ - 1) {
				structure_[ind][0][Row.N.ordinal()] = ind - rowSize_;
			} else {
				structure_[ind][0][Row.N.ordinal()] = (ind - rowSize_ + solutionSetSize) % solutionSetSize;
			}

			// East neighbors
			if ((ind + 1) % rowSize_ == 0)
				structure_[ind][0][Row.E.ordinal()] = (ind - (rowSize_ - 1));
			else
				structure_[ind][0][Row.E.ordinal()] = (ind + 1);

			// Western neigbors
			if (ind % rowSize_ == 0) {
				structure_[ind][0][Row.W.ordinal()] = (ind + (rowSize_ - 1));
			} else {
				structure_[ind][0][Row.W.ordinal()] = (ind - 1);
			}

			// South neigbors
			structure_[ind][0][Row.S.ordinal()] = (ind + rowSize_) % solutionSetSize;
		}

		for (int ind = 0; ind < solutionSetSize_; ind++) {
			structure_[ind][0][Row.NE.ordinal()] = structure_[structure_[ind][0][Row.N.ordinal()]][0][Row.E.ordinal()];
			structure_[ind][0][Row.NW.ordinal()] = structure_[structure_[ind][0][Row.N.ordinal()]][0][Row.W.ordinal()];
			structure_[ind][0][Row.SE.ordinal()] = structure_[structure_[ind][0][Row.S.ordinal()]][0][Row.E.ordinal()];
			structure_[ind][0][Row.SW.ordinal()] = structure_[structure_[ind][0][Row.S.ordinal()]][0][Row.W.ordinal()];
		}
	} // Neighborhood

	/**
	 * Returns a <code>SolutionSet</code> with the North, Sout, East and West
	 * neighbors solutions of ratio 0 of a given location into a given
	 * <code>SolutionSet</code>.
	 * 
	 * @param solutionSet
	 *            The <code>SolutionSet</code>.
	 * @param location
	 *            The location.
	 * @return a <code>SolutionSet</code> with the neighbors.
	 */
	public SolutionSet getFourNeighbors(SolutionSet solutionSet, int location) {
		// SolutionSet that contains the neighbors (to return)
		SolutionSet neighbors;

		// instance the solutionSet to a non dominated li of individuals
		neighbors = new SolutionSet(24);

		// Gets the neighboords N, S, E, W
		int index;

		// North
		index = structure_[location][0][Row.N.ordinal()];
		neighbors.add(solutionSet.get(index));

		// South
		index = structure_[location][0][Row.S.ordinal()];
		neighbors.add(solutionSet.get(index));

		// East
		index = structure_[location][0][Row.E.ordinal()];
		neighbors.add(solutionSet.get(index));

		// West
		index = structure_[location][0][Row.W.ordinal()];
		neighbors.add(solutionSet.get(index));

		// Return the list of non-dominated individuals
		return neighbors;
	} // getFourNeighbors

	/**
	 * Returns a <code>SolutionSet</code> with the North, Sout, East, West,
	 * North-West, South-West, North-East and South-East neighbors solutions of
	 * ratio 0 of a given location into a given <code>SolutionSet</code>.
	 * solutions of a given location into a given <code>SolutionSet</code>.
	 * 
	 * @param population
	 *            The <code>SolutionSet</code>.
	 * @param individual
	 *            The individual.
	 * @return a <code>SolutionSet</code> with the neighbors.
	 */
	public SolutionSet getEightNeighbors(SolutionSet population, int individual) {
		// SolutionSet that contains the neighbors (to return)
		SolutionSet neighbors;

		// instance the population to a non dominated li of individuals
		neighbors = new SolutionSet(24);

		// Gets the neighboords N, S, E, W
		int index;

		// N
		index = this.structure_[individual][0][Row.N.ordinal()];
		neighbors.add(population.get(index));

		// S
		index = this.structure_[individual][0][Row.S.ordinal()];
		neighbors.add(population.get(index));

		// E
		index = this.structure_[individual][0][Row.E.ordinal()];
		neighbors.add(population.get(index));

		// W
		index = this.structure_[individual][0][Row.W.ordinal()];
		neighbors.add(population.get(index));

		// NE
		index = this.structure_[individual][0][Row.NE.ordinal()];
		neighbors.add(population.get(index));

		// NW
		index = this.structure_[individual][0][Row.NW.ordinal()];
		neighbors.add(population.get(index));

		// SE
		index = this.structure_[individual][0][Row.SE.ordinal()];
		neighbors.add(population.get(index));

		// SW
		index = this.structure_[individual][0][Row.SW.ordinal()];
		neighbors.add(population.get(index));

		// Return the list of non-dominated individuals
		return neighbors;
	} // getEightNeighbors
} // Neighborhood
